#!/usr/local/bin/python3
# -*- coding: utf-8 -*-

"""
@File    : ui_stdf.py
@Author  : Link
@Time    : 2022/5/2 10:44
@Mark    : 
"""
import os

import win32api
import math
import datetime as dt
from typing import Union, List
from pydoc import help

import csv
import numpy as np
import pandas as pd
from PySide2.QtCore import Slot, Signal, QTimer, Qt
from PySide2.QtGui import QCloseEvent
from PySide2.QtWidgets import QMainWindow, QMessageBox, QFileDialog

from pyqtgraph.dockarea import *

from chart_core.chart_pyqtgraph.core.mixin import ChartType
from chart_core.chart_pyqtgraph.poll import ChartDockWindow
from common.app_variable import GlobalVariable
from common.data_class_interface.for_analysis_stdf import PTMD_HEAD, PRR_HEAD, DTP_HEAD
from common.func import tid_maker
from common.li import Li, SummaryCore, FutureLi
from report_core.html_utils.html_api import HtmlTableApi
from ui_component.ui_analysis_stdf.ui_components.ui_data_group import DataGroupWidget
from ui_component.ui_analysis_stdf.ui_components.ui_processing import ProcessWidget
from ui_component.ui_analysis_stdf.ui_structure import UiStdfStructure
from ui_component.ui_app_variable import UiGlobalVariable
from ui_component.ui_common.my_text_browser import Print
from ui_component.ui_common.ui_console import ConsoleWidget
from ui_component.ui_analysis_stdf.ui_designer.ui_home_load import Ui_MainWindow
from ui_component.ui_common.ui_utils import QTableUtils
from ui_component.ui_analysis_stdf.ui_components.ui_file_load_widget import FileLoadWidget  # 文件选取
from ui_component.ui_analysis_stdf.ui_components.ui_tree_load_widget import TreeLoadWidget  # 载入的数据选取
from ui_component.ui_analysis_stdf.ui_components.ui_table_load_widget import TableLoadWidget  # 测试项选取

import matplotlib as mpl

from var_language import BaseConfig

mpl.use('Qt5Agg')
import matplotlib.pyplot as plt

plt.rcParams["patch.force_edgecolor"] = True
plt.style.use('bmh')

mpl.rcParams["font.sans-serif"] = ["SimHei"]  # 展示中文字体
mpl.rcParams["axes.unicode_minus"] = False  # 处理负刻度值


class StdfLoadUi(QMainWindow, Ui_MainWindow):
    """
    懒加载界面
    """
    closeSignal = Signal(int)
    parent = None

    def __init__(self, parent=None, space_nm=1, path_select=False, select=True, is_web=False):
        super(StdfLoadUi, self).__init__(parent)
        self.setupUi(self)
        self.space_nm = space_nm
        self.li = FutureLi()
        self.summary = SummaryCore()
        self.title = "STDF数据载入空间: {}".format(space_nm)
        self.setWindowTitle(self.title)
        self.setAttribute(Qt.WA_DeleteOnClose)

        self.area = DockArea()
        self.setCentralWidget(self.area)
        " FileLoadWidget用来载入数据文件，内部用到多线程 "
        self.stdf_select_widget = FileLoadWidget(self.summary, self, space_nm=space_nm)
        self.dock_stdf_load = Dock("121 StdfFileSelect", size=(400, 100))
        self.dock_stdf_load.addWidget(self.stdf_select_widget)
        self.area.addDock(self.dock_stdf_load)

        " TreeLoadWidget用来载入和配对内部数据，会占用到大量的IO资源 "
        self.tree_load_widget = TreeLoadWidget(self.li, self.summary, self)
        self.dock_tree_load = Dock("122 DataTreeSelect", size=(200, 300))
        self.dock_tree_load.addWidget(self.tree_load_widget)
        self.area.addDock(self.dock_tree_load, "bottom", self.dock_stdf_load)
        # self.area.moveDock(self.dock_stdf_load, 'above', self.dock_tree_load)

        " TableLoadWidget用来载入和配对内部数据，会占用到大量的IO资源, 并且是作为主要的分析界面 "
        self.table_load_widget = TableLoadWidget(self.li, self.summary, self)
        self.dock_table_load = Dock("123 ItemDataAnalysis", size=(200, 300))
        self.dock_table_load.addWidget(self.table_load_widget)
        self.area.addDock(self.dock_table_load, "right", self.dock_tree_load)
        # self.area.moveDock(self.dock_tree_load, 'above', self.dock_table_load)

        " DataGroupWidget用来对数据进行简单的分组和筛选 "
        self.group_table_widget = DataGroupWidget(self.li)
        self.dock_group_load = Dock("124 DataGroupSet", size=(50, 300))
        self.dock_group_load.addWidget(self.group_table_widget)
        self.area.addDock(self.dock_group_load)

        text = """
        载入的功能包: 
            np(numpy), pd(pandas), math, os, help, win32api,
            import matplotlib.pyplot as plt,
            import matplotlib as mpl
            # 加入matplotlib作为特殊场景
        载入的数据:
            点击RUN后会被刷新 
            li: 数据空间的集合数据类, 通过help(li)查看
        RUN.
        """
        self.namespace = {
            "os": os,
            "win32api": win32api,
            "np": np,
            "pd": pd,
            "math": math,
            "help": help,
            "plt": plt,
            "mpl": mpl,
            "li": self.li,
            "summary": self.summary
        }
        self.console = ConsoleWidget(parent=self, namespace=self.namespace, text=text)
        self.dock_console_load = Dock("125 PythonConsole", size=(400, 100))
        self.dock_console_load.addWidget(self.console)
        self.area.addDock(self.dock_console_load, "bottom")
        # self.area.moveDock(self.dock_stdf_load, 'above', dock_console_load)

        # " 用来存放chart的 "
        # self.chart_ui = ChartDockWindow(self.li, None, icon=None, space_nm=space_nm)  # type:ChartDockWindow
        # self.dock_chart = Dock("Chart Dock", size=(400, 100))
        # self.dock_chart.addWidget(self.chart_ui)
        # self.area.addDock(self.dock_chart, "bottom")
        "------------------------------------------------------------------------------"

        "------------------------------------------------------------------------------"
        self.area.restoreState(UiStdfStructure.Default)
        self.dock_console_load.hide()  # 可以显示和隐藏
        self.init_signal()

        " 用来存放chart的 "
        self.chart_ui = ChartDockWindow(self.li, None, icon=None, space_nm=space_nm)  # type:ChartDockWindow

        " 用来算制程能力的 "
        self.process_ui = ProcessWidget(None, )

        self.action_cus_distribution.setVisible(False)

        if select and is_web is False:
            if path_select:
                self.stdf_select_widget.first_directory_select()
            else:
                self.stdf_select_widget.first_select()

        if BaseConfig.EN:
            self.retranslate()

    def init_signal(self):
        self.stdf_select_widget.finished.connect(self.tree_load_widget.set_tree)
        self.li.QCalculation.connect(self.q_calculation)
        self.li.QMessage.connect(self.message_show)
        self.li.QStatusMessage.connect(self.mdi_space_message_emit)

    def q_calculation(self):
        self.table_load_widget.cal_table()
        self.group_table_widget.checkbox_changed()

    @Slot(SummaryCore)
    def merge_data_emit(self, data: SummaryCore):
        self.summary = data
        self.tree_load_widget.set_tree()

    @Slot()
    def on_action_dock_structure_triggered(self):
        """ 将 area 布局保存在泡菜中 """
        state = self.area.saveState()
        print(state)

    def unchecked_all_trigger(self):
        self.action_default_widget.setChecked(Qt.Unchecked)
        self.action_hide_load_tree.setChecked(Qt.Unchecked)
        self.action_only_show_table.setChecked(Qt.Unchecked)
        self.action_console.setChecked(Qt.Unchecked)

    @Slot()
    def on_action_default_widget_triggered(self):
        self.unchecked_all_trigger()
        self.area.restoreState(UiStdfStructure.Default)
        self.action_default_widget.setChecked(Qt.Checked)
        self.dock_console_load.hide()

    @Slot()
    def on_action_hide_load_tree_triggered(self):
        self.unchecked_all_trigger()
        self.area.restoreState(UiStdfStructure.HideLoadWithTree)
        self.action_hide_load_tree.setChecked(Qt.Checked)
        self.dock_console_load.hide()

    @Slot()
    def on_action_only_show_table_triggered(self):
        self.unchecked_all_trigger()
        self.action_only_show_table.setChecked(Qt.Checked)
        self.area.restoreState(UiStdfStructure.OnlyTable)
        self.dock_console_load.hide()

    @Slot()
    def on_action_console_triggered(self):
        self.unchecked_all_trigger()
        self.action_console.setChecked(Qt.Checked)
        self.area.restoreState(UiStdfStructure.PythonConsole)
        self.dock_stdf_load.hide()
        self.dock_tree_load.hide()
        self.dock_console_load.show()

    @Slot()
    def on_action_limit_triggered(self):
        self.li.show_limit_diff()

    @Slot()
    def on_action_html_report_triggered(self):
        """
        将前几颗芯片的数据导出到Html报表中
        """
        top_qty = UiGlobalVariable.HtmlReportMaxRows
        if len(self.li.df_module.prr_df) > top_qty:
            df: pd.DataFrame = self.li.df_module.prr_df[:top_qty]
        else:
            df: pd.DataFrame = self.li.df_module.prr_df
        dtp = self.li.df_module.dtp_df[
            self.li.df_module.dtp_df.ID.isin(df.ID) & self.li.df_module.dtp_df.PART_ID.isin(df.PART_ID)
            ]
        if len(dtp) == 0:
            Print.Warning("No Data For Html Report Created")
            return
        file_title = "HtmlReport{}".format(tid_maker())
        html_path = os.path.join(GlobalVariable.SWAP_PATH, file_title + ".html")
        data_df = pd.merge(
            dtp, self.li.df_module.ptmd_df_limit[[
                "ID", "TEST_ID", "DATAT_TYPE", "TEST_NUM", "TEST_TXT", "UNITS", "NEW_TEST_ID"
            ]], on=["ID", "TEST_ID"]
        )
        for row in df.itertuples():  # type:PRR_HEAD
            row_head = (("GROUP", "TEST_QTY", "SITE_NUM", "X_COORD", "Y_COORD", "HARD_BIN", "SOFT_BIN", "RESULT",),)
            row_data = [
                (row.DA_GROUP, str(row.PART_ID), str(row.SITE_NUM), str(row.X_COORD), str(row.Y_COORD),
                 str(row.HARD_BIN), str(row.SOFT_BIN), "PASS" if row.FAIL_FLAG else "FAIL")
            ]
            HtmlTableApi(
                title="UUT{}_INFO".format(row.PART_ID),
                header_data=row_head,
                rows_data=row_data
            ).setAllStyle().setRowStyles().createHtml(html_path)

            t_df = data_df[(data_df.ID == row.ID) & (data_df.PART_ID == row.PART_ID)]
            t_row_head = (("TEST_ID", "TEST_NUM", "TEST_TXT", "UNITS", "LO_LIMIT", "VALUE", "HI_LIMIT", "RESULT"),)
            t_row_data = []
            for t_row in t_df.itertuples():  # type:DTP_HEAD
                t_row_data.append(
                    (str(t_row.TEST_ID), str(t_row.TEST_NUM), t_row.TEST_TXT, t_row.UNITS,
                     str(
                         round(t_row.LO_LIMIT, UiGlobalVariable.GraphPlotFloatRound)
                     ), str(
                        round(t_row.RESULT, UiGlobalVariable.GraphPlotFloatRound)
                    ), str(
                        round(t_row.HI_LIMIT, UiGlobalVariable.GraphPlotFloatRound)
                    ), "PASS" if t_row.FAIL_FLG else "FAIL"
                     )
                )
            HtmlTableApi(
                title="UUT{}_DATA".format(row.PART_ID),
                header_data=t_row_head,
                rows_data=t_row_data
            ).setAllStyle().setRowStyles().createHtml(html_path)
            HtmlTableApi.AppendWriteHtml(html_path, "<br>")

        win32api.ShellExecute(0, 'open', html_path, '', '', 1)

    @Slot(str)
    def mdi_space_message_emit(self, message: str):
        """
        append message
        :param message:
        :return:
        """
        self.statusbar.showMessage("==={}==={}===".format(dt.datetime.now().strftime("%H:%M:%S"), message))

    def message_show(self, text: str) -> bool:
        res = QMessageBox.question(self, '待确认', text,
                                   QMessageBox.Yes | QMessageBox.No,
                                   QMessageBox.Yes)
        if res == QMessageBox.Yes:
            return True
        else:
            return False

    def get_test_id_column(self) -> Union[List[int], None]:
        """

        :return:
        """
        return QTableUtils.getTableWidgetTestId(self.table_load_widget.cpk_info_table)

    def get_all_test_id_column(self) -> Union[List[int], None]:
        """

        :return:
        """
        return QTableUtils.getTableWidgetAllTestId(self.table_load_widget.cpk_info_table)

    def get_text_column(self) -> Union[List[str], None]:
        """

        :return:
        """
        test_id_column = self.get_test_id_column()
        if not test_id_column:
            return None
        text_column = []
        for each in test_id_column:
            table_row = self.li.capability_key_dict[each]
            text = str(table_row["TEST_NUM"]) + ":" + table_row["TEST_TXT"]
            text_column.append(text)
        return text_column

    def get_select_data_to_csv_or_jmp_or_altair(self, no_test_id: bool = False) -> (pd.DataFrame, dict):
        if no_test_id:
            return self.li.get_unstack_data_to_csv_or_jmp_or_altair([])
        test_id_list = self.get_test_id_column()
        if test_id_list is None:
            self.message_show("未选取测试项目无法进行图形或数据生成@")
            return
        return self.li.get_unstack_data_to_csv_or_jmp_or_altair(test_id_list)

    def get_all_data_to_csv_or_jmp_or_altair(self):
        test_id_list = self.get_all_test_id_column()
        return self.li.get_unstack_data_to_csv_or_jmp_or_altair(test_id_list)

    def get_data_use_processed(self):
        test_id_list = self.get_test_id_column()
        if test_id_list is None:
            test_id_list = []
        return self.li.get_unstack_data_to_csv_or_jmp_or_altair(test_id_list)

    @Slot()
    def on_action_qt_distribution_trans_triggered(self):
        """ 使用PYQT来拉出横向柱状分布图 """
        test_id_column: List[int] = self.get_test_id_column()
        self.chart_ui.add_chart_dock(test_id_column, ChartType.TransBar)
        self.chart_show()

    @Slot()
    def on_action_qt_scatter_triggered(self):
        """ 使用PYQT来拉出线性散点图 """
        test_id_column: List[int] = self.get_test_id_column()
        self.chart_ui.add_chart_dock(test_id_column, ChartType.TransScatter)
        self.chart_show()

    @Slot()
    def on_action_qt_mapping_triggered(self):
        """ 使用PYQT来拉出Mapping图 """
        pass

    @Slot()
    def on_action_qt_visual_map_triggered(self):
        """ 使用PYQT来拉出Visual Map图 """
        test_id_column = self.get_test_id_column()
        self.chart_ui.add_chart_dock(test_id_column, ChartType.VisualMap)
        self.chart_show()

    @Slot()
    def on_action_log_text_triggered(self):
        """
        导出Logger
        """
        self.chart_ui.add_chart_dock(None, ChartType.DataLog)
        self.chart_show()

    def chart_show(self):
        if self.chart_ui.isHidden():
            self.chart_ui.show()
        else:
            self.chart_ui.raise_()

    @Slot()
    def on_action_processing_report_triggered(self):
        """
        制程能力报告, 先选取到选取到的数据, 然后生成制程能力报告, 稍微会复杂一些
        :return:
        """
        data = self.get_data_use_processed()
        if data is None:
            return Print.Warning("无数据作用@!!!")
        jmp_df, temp_calculation = data
        self.process_ui.set_data(jmp_df, temp_calculation)
        if self.process_ui.isHidden():
            self.process_ui.show()
        else:
            self.process_ui.raise_()

    @Slot()
    def on_action_processing_excel_triggered(self):
        """
        通过多线程操作li, 线程放在table_widget中,
        """

    @staticmethod
    def csv_calculation_write(save_file: str, calculation: dict):
        """
        写入csv数据的表头
        """
        csv_head_rows = [PTMD_HEAD.TEXT, PTMD_HEAD.TEST_NUM, PTMD_HEAD.TEST_TXT, PTMD_HEAD.LO_LIMIT, PTMD_HEAD.HI_LIMIT,
                         PTMD_HEAD.UNITS, "LO_LIMIT_TYPE", "HI_LIMIT_TYPE", PTMD_HEAD.DATAT_TYPE]
        rows = []
        for row_head in csv_head_rows:
            temp_list = []
            for i in range(GlobalVariable.CSV_LIMIT_START_COLUMN):
                temp_list.append("")
            temp_list.append(row_head)
            for _, value in calculation.items():
                temp_list.append(value[row_head])
            rows.append(temp_list)
        with open(save_file, 'w', newline='') as file:
            writer = csv.writer(file, delimiter=",")
            writer.writerows(rows)

    @Slot()
    def on_action_save_select_data_triggered(self):
        """
        保存筛选DIE的选取项目测试项目的测试数据
        分为csv和excel模式
        注意是选取项目
        """
        if self.li.to_chart_csv_data.df is None:
            return self.message_show('无数据, 无法保存!!! ')
        save_file, suffix = QFileDialog.getSaveFileName(None,
                                                        caption='保存文件',
                                                        dir=GlobalVariable.SWAP_PATH,
                                                        filter='csv(*.csv)')
        if suffix not in {'csv(*.csv)', 'excel(*.xlsm)'}:
            self.statusbar.showMessage('取消数据保存')
            return
        csv_df, temp_calculation = self.get_select_data_to_csv_or_jmp_or_altair()
        if suffix == 'csv(*.csv)':
            self.csv_calculation_write(save_file=save_file, calculation=temp_calculation)
            self.save_append_df_to_csv(data_object=csv_df, file_path=save_file)
        if suffix == 'excel(*.xlsm)':
            self.save_df_to_excel(csv_df, temp_calculation, save_file)

    @Slot()
    def on_action_sava_data_triggered(self):
        """
        保存筛选DIE的所有项目测试数据
        分为csv和excel模式
        注意是所有项目
        如果不筛选, 就是所有的数据
        """
        if self.li.to_chart_csv_data.df is None:
            return self.message_show('无数据, 无法保存!!! ')
        save_file, suffix = QFileDialog.getSaveFileName(None,
                                                        caption='保存文件',
                                                        dir=GlobalVariable.SWAP_PATH,
                                                        filter='csv(*.csv)')
        if suffix not in {'csv(*.csv)', 'excel(*.xlsm)'}:
            self.statusbar.showMessage('取消数据保存')
            return
        csv_df, temp_calculation = self.get_all_data_to_csv_or_jmp_or_altair()
        if suffix == 'csv(*.csv)':
            self.csv_calculation_write(save_file=save_file, calculation=temp_calculation)
            self.save_append_df_to_csv(data_object=csv_df, file_path=save_file)
        if suffix == 'excel(*.xlsm)':
            return

    def save_append_df_to_csv(self, data_object: pd.DataFrame, file_path) -> bool:
        """
        追加数据保存模型
        """
        if data_object is None:
            self.mdi_space_message_emit('未查询 空数据无法保存!!! ')
            return False
        if any(data_object):
            data_object.sort_values(by=["ALL_GROUP", "PART_ID"], inplace=True)
            data_object.to_csv(file_path, mode='a', encoding='utf_8_sig', index=False)
            self.mdi_space_message_emit(f'数据保存成功,路径在:>>>{file_path}, 开始自动打开文件.')
            win32api.ShellExecute(0, 'open', file_path, '', '', 1)
            return True
        else:
            self.mdi_space_message_emit('未查询 空数据无法保存!!! ')
            return False

    def closeEvent(self, a0: QCloseEvent) -> None:
        """
        删除mdi时, 需要将与其对应的chart也删除
        """
        self.closeSignal.emit(self.space_nm)
        self.chart_ui.close()
        self.chart_ui = None
        self.li = None
        self.summary = None
        return super(StdfLoadUi, self).closeEvent(a0)
